/**
 * Copyright (C) 2010-2016 eBusiness Information, Excilys Group
 * Copyright (C) 2016-2020 the ohosannotations project
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed To in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package org.ohosannotations.helper;

import static com.helger.jcodemodel.JExpr._new;
import static com.helger.jcodemodel.JExpr.lit;
import static org.ohosannotations.helper.ModelConstants.classSuffix;

import java.io.StringWriter;
import java.lang.annotation.Annotation;
import java.lang.annotation.Inherited;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.WildcardType;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Types;

import org.ohosannotations.OhosAnnotationsEnvironment;
import org.ohosannotations.annotations.EBean;
import org.ohosannotations.holder.EBeanHolder;
import org.ohosannotations.holder.GeneratedClassHolder;
import org.ohosannotations.internal.helper.AnnotationParamExtractor;

import com.helger.jcodemodel.AbstractJAnnotationValue;
import com.helger.jcodemodel.AbstractJClass;
import com.helger.jcodemodel.AbstractJType;
import com.helger.jcodemodel.IJAnnotatable;
import com.helger.jcodemodel.IJExpression;
import com.helger.jcodemodel.IJGenerifiable;
import com.helger.jcodemodel.IJStatement;
import com.helger.jcodemodel.JAnnotationArrayMember;
import com.helger.jcodemodel.JAnnotationUse;
import com.helger.jcodemodel.JBlock;
import com.helger.jcodemodel.JCodeModel;
import com.helger.jcodemodel.JDefinedClass;
import com.helger.jcodemodel.JExpr;
import com.helger.jcodemodel.JFormatter;
import com.helger.jcodemodel.JInvocation;
import com.helger.jcodemodel.JMethod;
import com.helger.jcodemodel.JMod;
import com.helger.jcodemodel.JTypeVar;
import com.helger.jcodemodel.JVar;

/**
 * aptcode模型辅助
 * APTCodeModelHelper
 *
 * @author dev
 * @since 2021-07-23
 */
public class APTCodeModelHelper {
    private static final List<String> IGNORED_ANNOTATIONS = Collections.singletonList("kotlin.Metadata");

    private OhosAnnotationsEnvironment environment;

    /**
     * APTCodeModelHelper
     *
     * @param environment environment
     */
    public APTCodeModelHelper(OhosAnnotationsEnvironment environment) {
        this.environment = environment;
    }

    /**
     * typeMirrorToJClass
     *
     * @param type type
     * @return typeMirrorToJClass
     */
    public AbstractJClass typeMirrorToJClass(TypeMirror type) {
        return typeMirrorToJClass(type, Collections.<String, TypeMirror>emptyMap());
    }

    /**
     * typeMirrorToJClass
     *
     * @param type type
     * @param substitute substitute
     * @return environment
     */
    private AbstractJClass typeMirrorToJClass(TypeMirror type, Map<String, TypeMirror> substitute) {
        if (type instanceof DeclaredType) {
            return typeMirrorToJClass((DeclaredType) type, substitute);
        } else if (type instanceof WildcardType) {
            return typeMirrorToJClass((WildcardType) type, substitute);
        } else if (type instanceof ArrayType) {
            return typeMirrorToJClass((ArrayType) type, substitute);
        } else {
            TypeMirror substituted = substitute.get(type.toString());
            if (substituted != null && type != substituted) {
                return typeMirrorToJClass(substituted, substitute);
            }
            return environment.getJClass(type.toString());
        }
    }

    /**
     * typeMirrorToJClass
     *
     * @param declaredType declaredType
     * @param substitute substitute
     * @return declaredClass
     */
    private AbstractJClass typeMirrorToJClass(DeclaredType declaredType, Map<String, TypeMirror> substitute) {
        String declaredTypeName = declaredType.asElement().toString();

        AbstractJClass declaredClass = environment.getJClass(declaredTypeName);

        List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();

        List<AbstractJClass> typeArgumentJClasses = new ArrayList<>();
        for (TypeMirror typeArgument : typeArguments) {
            typeArgumentJClasses.add(typeMirrorToJClass(typeArgument, substitute));
        }
        if (typeArgumentJClasses.size() > 0) {
            declaredClass = declaredClass.narrow(typeArgumentJClasses);
        }

        return declaredClass;
    }

    /**
     * typeMirrorToJClass
     *
     * @param wildcardType wildcardType
     * @param substitute substitute
     * @return typeMirrorToJClass
     */
    private AbstractJClass typeMirrorToJClass(WildcardType wildcardType, Map<String, TypeMirror> substitute) {
        TypeMirror bound = wildcardType.getExtendsBound();
        if (bound == null) {
            bound = wildcardType.getSuperBound();
            if (bound == null) {
                return environment.getClasses().OBJECT.wildcardExtends();
            }
            return typeMirrorToJClass(bound, substitute).wildcardSuper();
        }

        TypeMirror extendsBound = wildcardType.getExtendsBound();

        if (extendsBound == null) {
            return environment.getClasses().OBJECT.wildcardExtends();
        } else {
            return typeMirrorToJClass(extendsBound, substitute).wildcardExtends();
        }
    }

    /**
     * typeMirrorToJClass
     *
     * @param arrayType arrayType
     * @param substitute substitute
     * @return refClass
     */
    private AbstractJClass typeMirrorToJClass(ArrayType arrayType, Map<String, TypeMirror> substitute) {
        AbstractJClass refClass = typeMirrorToJClass(arrayType.getComponentType(), substitute);
        return refClass.array();
    }

    /**
     * getActualTypes
     *
     * @param typeUtils typeUtils
     * @param baseClass baseClass
     * @param annotatedClass annotatedClass
     * @return Collections
     */
    private Map<String, TypeMirror> getActualTypes(Types typeUtils,
        DeclaredType baseClass, TypeMirror annotatedClass) {
        List<TypeMirror> superTypes = new ArrayList<>();
        superTypes.add(annotatedClass);
        while (!superTypes.isEmpty()) {
            TypeMirror x1 = superTypes.remove(0);
            if (typeUtils.isSameType(typeUtils.erasure(x1), typeUtils.erasure(baseClass))) {
                DeclaredType type = (DeclaredType) x1;
                Map<String, TypeMirror> actualTypes = new HashMap<>();
                for (int i = 0; i < type.getTypeArguments().size(); i++) {
                    TypeMirror actualArg = type.getTypeArguments().get(i);
                    TypeMirror formalArg = baseClass.getTypeArguments().get(i);
                    if (!typeUtils.isSameType(actualArg, formalArg)) {
                        actualTypes.put(formalArg.toString(), actualArg);
                    }
                }
                return actualTypes;
            }
            superTypes.addAll(typeUtils.directSupertypes(x1));
        }
        return Collections.emptyMap();
    }

    /**
     * typeBoundsToJClass
     *
     * @param bounds bounds
     * @return typeBoundsToJClass
     */
    public List<AbstractJClass> typeBoundsToJClass(List<? extends TypeMirror> bounds) {
        return typeBoundsToJClass(bounds, Collections.<String, TypeMirror>emptyMap());
    }

    /**
     * typeBoundsToJClass
     *
     * @param bounds bounds
     * @param actualTypes actualTypes
     * @return jClassBounds
     */
    private List<AbstractJClass> typeBoundsToJClass(List<? extends TypeMirror> bounds,
        Map<String, TypeMirror> actualTypes) {
        if (bounds.isEmpty()) {
            return Collections.singletonList(environment.getClasses().OBJECT);
        } else {
            List<AbstractJClass> jClassBounds = new ArrayList<>();

            for (TypeMirror bound : bounds) {
                jClassBounds.add(typeMirrorToJClass(bound, actualTypes));
            }
            return jClassBounds;
        }
    }

    /**
     * addTypeBounds
     *
     * @param generifiable generifiable
     * @param bounds bounds
     * @param name name
     */
    private void addTypeBounds(IJGenerifiable generifiable, List<AbstractJClass> bounds, String name) {
        JTypeVar typeVar = null;

        for (AbstractJClass bound : bounds) {
            if (typeVar == null) {
                typeVar = generifiable.generify(name, bound);
            } else {
                typeVar.bound(bound);
            }
        }
    }

    /**
     * overrideAnnotatedMethod
     *
     * @param executableElement executableElement
     * @param holder holder
     * @return method
     */
    public JMethod overrideAnnotatedMethod(ExecutableElement executableElement, GeneratedClassHolder holder) {
        TypeMirror annotatedClass = holder.getAnnotatedElement().asType();
        DeclaredType baseClass = (DeclaredType) executableElement.getEnclosingElement().asType();

        Types typeUtils = environment.getProcessingEnvironment().getTypeUtils();

        Map<String, TypeMirror> actualTypes = getActualTypes(typeUtils, baseClass, annotatedClass);
        Map<String, List<AbstractJClass>> methodTypes = new LinkedHashMap<>();

        for (TypeParameterElement typeParameter : executableElement.getTypeParameters()) {
            List<? extends TypeMirror> bounds = typeParameter.getBounds();

            List<AbstractJClass> addedBounds = typeBoundsToJClass(bounds, actualTypes);
            methodTypes.put(typeParameter.toString(), addedBounds);
        }

        actualTypes.keySet().removeAll(methodTypes.keySet());

        JMethod existingMethod = findAlreadyGeneratedMethod(executableElement, holder);
        if (existingMethod != null) {
            return existingMethod;
        }

        String methodName = executableElement.getSimpleName().toString();
        AbstractJClass returnType = typeMirrorToJClass(executableElement.getReturnType(), actualTypes);
        int modifier = elementVisibilityModifierToJMod(executableElement);
        JMethod method = holder.getGeneratedClass().method(modifier, returnType, methodName);
        copyNonAAAnnotations(method, executableElement.getAnnotationMirrors());

        if (!hasAnnotation(method, Override.class)) {
            method.annotate(Override.class);
        }

        for (Map.Entry<String, List<AbstractJClass>> typeDeclaration : methodTypes.entrySet()) {
            List<AbstractJClass> bounds = typeDeclaration.getValue();
            addTypeBounds(method, bounds, typeDeclaration.getKey());
        }

        int i1 = 0;
        for (VariableElement parameter : executableElement.getParameters()) {
            boolean varParam = i1 == executableElement.getParameters().size() - 1 && executableElement.isVarArgs();
            addParamToMethod(method, parameter, JMod.FINAL, actualTypes, varParam);
            i1++;
        }

        for (TypeMirror superThrownType : executableElement.getThrownTypes()) {
            AbstractJClass thrownType = typeMirrorToJClass(superThrownType, actualTypes);
            method._throws(thrownType);
        }

        callSuperMethod(method, holder, method.body());

        return method;
    }

    /**
     * ToJMod
     *
     * @param element element
     * @return JMod
     */
    public int elementVisibilityModifierToJMod(Element element) {
        Set<Modifier> modifiers = element.getModifiers();

        if (modifiers.contains(Modifier.PUBLIC)) {
            return JMod.PUBLIC;
        } else if (modifiers.contains(Modifier.PROTECTED)) {
            return JMod.PROTECTED;
        } else if (modifiers.contains(Modifier.PRIVATE)) {
            return JMod.PRIVATE;
        } else {
            return JMod.NONE;
        }
    }

    /**
     * generify
     *
     * @param generifiable generifiable
     * @param fromTypeParameters fromTypeParameters
     */
    public void generify(IJGenerifiable generifiable, TypeElement fromTypeParameters) {
        for (TypeParameterElement param : fromTypeParameters.getTypeParameters()) {
            List<AbstractJClass> bounds = typeBoundsToJClass(param.getBounds());

            addTypeBounds(generifiable, bounds, param.getSimpleName().toString());
        }
    }

    /**
     * narrowGeneratedClass
     *
     * @param generatedClass generatedClass
     * @param fromTypeArguments fromTypeArguments
     * @return generatedClass
     */
    public AbstractJClass narrowGeneratedClass(AbstractJClass generatedClass, TypeMirror fromTypeArguments) {
        DeclaredType type = (DeclaredType) fromTypeArguments;

        for (TypeMirror param : type.getTypeArguments()) {
            AbstractJClass paramClass = typeMirrorToJClass(param);
            generatedClass = generatedClass.narrow(paramClass);
        }
        return generatedClass;
    }

    /**
     * findAlreadyGeneratedMethod
     *
     * @param executableElement executableElement
     * @param holder holder
     * @return null
     */
    private JMethod findAlreadyGeneratedMethod(ExecutableElement executableElement, GeneratedClassHolder holder) {
        JDefinedClass definedClass = holder.getGeneratedClass();
        String methodName = executableElement.getSimpleName().toString();
        List<? extends VariableElement> parameters = executableElement.getParameters();
        method:
        for (JMethod method : definedClass.methods()) {
            if (method.name().equals(methodName) && method.params().size() == parameters.size()) {
                int i1 = 0;
                for (JVar param : method.params()) {
                    String searchedParamType = typeMirrorToJClass(parameters.get(i1).asType()).fullName();
                    if (!param.type().fullName().equals(searchedParamType)) {
                        continue method;
                    }
                    i1++;
                }
                return method;
            }
        }
        return null;
    }

    /**
     * addParamToMethod
     *
     * @param method method
     * @param parameter parameter
     * @param mod mod
     * @param actualTypes actualTypes
     * @param varParam varParam
     */
    private void addParamToMethod(JMethod method, VariableElement parameter,
        int mod, Map<String, TypeMirror> actualTypes, boolean varParam) {
        String parameterName = parameter.getSimpleName().toString();
        AbstractJClass parameterClass = typeMirrorToJClass(parameter.asType(), actualTypes);
        JVar param = varParam ? method.varParam(mod, parameterClass
            .elementType(), parameterName) : method.param(mod, parameterClass, parameterName);
        copyNonAAAnnotations(param, parameter.getAnnotationMirrors());
    }

    /**
     * copyNonAAAnnotations
     *
     * @param annotatable annotatable
     * @param annotationMirrors annotationMirrors
     */
    public void copyNonAAAnnotations(IJAnnotatable annotatable, List<? extends AnnotationMirror> annotationMirrors) {
        for (AnnotationMirror annotationMirror : annotationMirrors) {
            if (annotationMirror.getAnnotationType().asElement().getAnnotation(Inherited.class) == null) {
                AbstractJClass annotationClass = typeMirrorToJClass(annotationMirror.getAnnotationType());
                if (!environment.isOhosAnnotation(annotationClass.fullName())
                    && !IGNORED_ANNOTATIONS.contains(annotationClass.fullName())) {
                    copyAnnotation(annotatable, annotationMirror);
                }
            }
        }
    }

    /**
     * copyAnnotation
     *
     * @param annotatable annotatable
     * @param annotationMirror annotationMirror
     */
    public void copyAnnotation(IJAnnotatable annotatable, AnnotationMirror annotationMirror) {
        Map<? extends ExecutableElement, ? extends AnnotationValue> parameters = annotationMirror.getElementValues();

        if (!hasAnnotation(annotatable, annotationMirror) || annotatable instanceof JAnnotationArrayMember) {
            AbstractJClass annotation = typeMirrorToJClass(annotationMirror.getAnnotationType());
            JAnnotationUse annotate = annotatable.annotate(annotation);

            for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> param : parameters.entrySet()) {
                param.getValue()
                    .accept(new AnnotationParamExtractor(annotate, this), param.getKey().getSimpleName().toString());
            }
        }
    }

    /**
     * hasAnnotation
     *
     * @param annotatable annotatable
     * @param annotationMirror annotationMirror
     * @return hasAnnotation
     */
    private boolean hasAnnotation(IJAnnotatable annotatable, AnnotationMirror annotationMirror) {
        return hasAnnotation(annotatable, annotationMirror.getAnnotationType().toString());
    }

    /**
     * hasAnnotation
     *
     * @param annotatable annotatable
     * @param annotationClass annotationClass
     * @return hasAnnotation
     */
    public boolean hasAnnotation(IJAnnotatable annotatable, Class<? extends Annotation> annotationClass) {
        return hasAnnotation(annotatable, annotationClass.getCanonicalName());
    }

    /**
     * hasAnnotation
     *
     * @param annotatable annotatable
     * @param annotationFQN annotationFQN
     * @return false
     */
    private boolean hasAnnotation(IJAnnotatable annotatable, String annotationFQN) {
        for (JAnnotationUse annotation : annotatable.annotations()) {
            if (annotation.getAnnotationClass().fullName().equals(annotationFQN)) {
                return true;
            }
        }
        return false;
    }

    /**
     * getSuperCall
     *
     * @param holder holder
     * @param superMethod superMethod
     * @return superCall
     */
    public JInvocation getSuperCall(GeneratedClassHolder holder, JMethod superMethod) {
        IJExpression abilitySuper = holder.getGeneratedClass().staticRef("super");
        JInvocation superCall = JExpr.invoke(abilitySuper, superMethod);

        for (JVar param : superMethod.params()) {
            superCall.arg(param);
        }

        if (superMethod.hasVarArgs()) {
            superCall.arg(superMethod.varParam());
        }
        return superCall;
    }

    /**
     * callSuperMethod
     *
     * @param superMethod superMethod
     * @param holder holder
     * @param callBlock callBlock
     */
    public void callSuperMethod(JMethod superMethod, GeneratedClassHolder holder, JBlock callBlock) {
        JInvocation superCall = getSuperCall(holder, superMethod);

        AbstractJType returnType = superMethod.type();
        if (returnType.fullName().equals("void")) {
            callBlock.add(superCall);
        } else {
            callBlock._return(superCall);
        }
    }

    /**
     * removeBody
     *
     * @param method method
     * @return clonedBody
     */
    public JBlock removeBody(JMethod method) {
        JBlock body = method.body();
        try {
            Field bodyField = JMethod.class.getDeclaredField("m_aBody");
            bodyField.setAccessible(true);
            bodyField.set(method, null);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }

        JBlock clonedBody = new JBlock().bracesRequired(false).indentRequired(false);
        copy(body, clonedBody);
        return clonedBody;
    }

    /**
     * body
     *
     * @param body body
     * @param newBody newBody
     */
    public void copy(JBlock body, JBlock newBody) {
        for (Object statement : body.getContents()) {
            if (statement instanceof JVar) {
                JVar var = (JVar) statement;
                try {
                    Field varInitField = JVar.class.getDeclaredField("m_aInitExpr");
                    varInitField.setAccessible(true);
                    IJExpression varInit = (IJExpression) varInitField.get(var);

                    newBody.decl(var.type(), var.name(), varInit);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            } else {
                newBody.add((IJStatement) statement);
            }
        }
    }

    /**
     * replaceSuperCall
     *
     * @param method method
     * @param replacement replacement
     */
    public void replaceSuperCall(JMethod method, JBlock replacement) {
        String superCallStart = "super." + method.name() + "(";

        JBlock oldBody = removeBody(method);
        JBlock newBody = method.body();

        for (Object content : oldBody.getContents()) {
            StringWriter writer = new StringWriter();
            JFormatter formatter = new JFormatter(writer);
            IJStatement statement = (IJStatement) content;
            statement.state(formatter);
            String statementString = writer.getBuffer().toString();
            if (statementString.startsWith(superCallStart)) {
                newBody.add(replacement);
            } else {
                newBody.add(statement);
            }
        }
    }

    /**
     * RunnableClass
     *
     * @param previousBody previousBody
     * @return anonymousRunnableClass
     */
    public JDefinedClass createDelegatingAnonymousRunnableClass(JBlock previousBody) {
        JCodeModel codeModel = environment.getCodeModel();

        JDefinedClass anonymousRunnableClass = codeModel.anonymousClass(Runnable.class);

        JMethod runMethod = anonymousRunnableClass.method(JMod.PUBLIC, codeModel.VOID, "run");
        runMethod.annotate(Override.class);
        runMethod.body().add(previousBody);

        return anonymousRunnableClass;
    }

    /**
     * Gets all of the methods of the class and includes the methods of any
     * implemented interfaces.
     *
     * @param typeElement typeElement
     * @return full list of methods.
     */
    public List<ExecutableElement> getMethods(TypeElement typeElement) {
        List<? extends Element> enclosedElements = typeElement.getEnclosedElements();
        List<ExecutableElement> methods = new ArrayList<>(ElementFilter.methodsIn(enclosedElements));

        // Add methods of the interfaces. These will be valid as they have gone
        // through the validator.
        for (TypeMirror iface : typeElement.getInterfaces()) {
            DeclaredType dt = (DeclaredType) iface;
            methods.addAll(ElementFilter.methodsIn(dt.asElement().getEnclosedElements()));
        }

        return methods;
    }

    /**
     * implementMethod
     *
     * @param holder holder
     * @param methods methods
     * @param methodName methodName
     * @param returnType returnType
     * @param parameterTypes parameterTypes
     * @return implementMethod
     */
    public JMethod implementMethod(GeneratedClassHolder holder,
        List<ExecutableElement> methods, String methodName, String returnType, String... parameterTypes) {
        return implementMethod(holder, methods, methodName, returnType, false, parameterTypes);
    }

    /**
     * implementMethod
     *
     * @param holder holder
     * @param methods methods
     * @param methodName methodName
     * @param returnType returnType
     * @param finalParams finalParams
     * @param parameterTypes parameterTypes
     * @return jmethod
     */
    public JMethod implementMethod(GeneratedClassHolder holder,
        List<ExecutableElement> methods, String methodName,
        String returnType, boolean finalParams, String... parameterTypes) {
        ExecutableElement method = getMethod(methods, methodName, returnType, parameterTypes);
        JMethod jmethod = null;

        if (method != null) {
            AbstractJType jcReturnType = returnType.equals(TypeKind.VOID.toString())
                ? environment.getCodeModel().VOID : environment.getJClass(returnType);

            jmethod = holder.getGeneratedClass().method(JMod.PUBLIC, jcReturnType, method.getSimpleName().toString());
            jmethod.annotate(Override.class);

            int paramMods = finalParams ? JMod.FINAL : JMod.NONE;
            for (int i = 0; i < method.getParameters().size(); i++) {
                VariableElement param = method.getParameters().get(i);
                jmethod.param(paramMods, environment.getJClass(parameterTypes[i]), param.getSimpleName().toString());
            }
        }
        return jmethod;
    }

    /**
     * getMethod
     *
     * @param methods methods
     * @param methodName methodName
     * @param returnType returnType
     * @param parameterTypes parameterTypes
     * @return null
     */
    private ExecutableElement getMethod(List<ExecutableElement> methods,
        String methodName, String returnType, String... parameterTypes) {
        for (ExecutableElement method : methods) {
            List<? extends VariableElement> parameters = method.getParameters();

            // Get the method return type or "VOID" if none.
            String methodReturnType = method.getReturnType().getKind()
                == TypeKind.VOID ? TypeKind.VOID.toString() : method.getReturnType().toString();

            if (parameters.size() == parameterTypes.length && methodReturnType.equals(returnType)) {
                if (methodName == null || method.getSimpleName().toString().equals(methodName)) {
                    boolean validMethod = true;

                    for (int i = 0; i < parameters.size(); i++) {
                        VariableElement param = parameters.get(i);

                        if (!param.asType().toString().equals(parameterTypes[i])) {
                            validMethod = false;
                            break;
                        }
                    }

                    if (validMethod) {
                        return method;
                    }
                }
            }
        }

        return null;
    }

    /**
     * newBeanOrEBean
     *
     * @param beanType beanType
     * @param contextVar contextVar
     * @return _new
     */
    public JInvocation newBeanOrEBean(DeclaredType beanType, JVar contextVar) {
        if (beanType.asElement().getAnnotation(EBean.class) != null) {
            String typeQualifiedName = beanType.toString();
            AbstractJClass injectedClass = environment
                .getJClass(typeQualifiedName + classSuffix());
            return injectedClass.staticInvoke(EBeanHolder.GET_INSTANCE_METHOD_NAME).arg(contextVar);
        } else {
            return _new(environment.getJClass(beanType.toString()));
        }
    }

    /**
     * litObject
     *
     * @param o1 o
     * @return lit
     */
    public IJExpression litObject(Object o1) {
        if (o1 instanceof Integer) {
            return lit((Integer) o1);
        } else if (o1 instanceof Float) {
            return lit((Float) o1);
        } else if (o1 instanceof Long) {
            return lit((Long) o1);
        } else if (o1 instanceof Boolean) {
            return lit((Boolean) o1);
        } else {
            return lit((String) o1);
        }
    }

    /**
     * getActualType
     *
     * @param element element
     * @param holder holder
     * @return getActualType
     */
    public TypeMirror getActualType(Element element, GeneratedClassHolder holder) {
        DeclaredType enclosingClassType = (DeclaredType) element.getEnclosingElement().asType();
        return getActualType(element, enclosingClassType, holder);
    }

    /**
     * getActualType
     *
     * @param element element
     * @param enclosingClassType enclosingClassType
     * @param holder holder
     * @return type
     */
    public TypeMirror getActualType(Element element, DeclaredType enclosingClassType, GeneratedClassHolder holder) {
        Types types = environment.getProcessingEnvironment().getTypeUtils();
        TypeMirror annotatedClass = holder.getAnnotatedElement().asType();

        Map<String, TypeMirror> actualTypes = getActualTypes(types, enclosingClassType, annotatedClass);

        TypeMirror type = actualTypes.get(element.asType().toString());
        return type == null ? element.asType() : type;
    }

    /**
     * InjectedElement
     *
     * @param holder holder
     * @param param param
     * @return getActualType
     */
    public TypeMirror getActualTypeOfEnclosingElementOfInjectedElement(GeneratedClassHolder holder, Element param) {
        DeclaredType enclosingClassType;
        if (param.getKind() == ElementKind.PARAMETER) {
            enclosingClassType = (DeclaredType) param.getEnclosingElement().getEnclosingElement().asType();
        } else {
            enclosingClassType = (DeclaredType) param.getEnclosingElement().asType();
        }
        return getActualType(param, enclosingClassType, holder);
    }

    /**
     * addSuppressWarnings
     *
     * @param generatedElement generatedElement
     * @param annotationValue annotationValue
     */
    public void addSuppressWarnings(IJAnnotatable generatedElement, String annotationValue) {
        Collection<JAnnotationUse> annotations = generatedElement.annotations();
        for (JAnnotationUse annotationUse : annotations) {
            if (SuppressWarnings.class.getCanonicalName().equals(annotationUse.getAnnotationClass().fullName())) {
                AbstractJAnnotationValue value = annotationUse.getParam("value");
                StringWriter code = new StringWriter();
                JFormatter formatter = new JFormatter(code);
                formatter.generable(value);
                if (!code.toString().contains(annotationValue)) {
                    if (value instanceof JAnnotationArrayMember) {
                        ((JAnnotationArrayMember) value).param(annotationValue);
                    } else {
                        String foundValue = code.toString().substring(1, code.toString().length() - 1);
                        JAnnotationArrayMember newParamArray = annotationUse.paramArray("value");
                        newParamArray.param(foundValue).param(annotationValue);
                    }
                }
                return;
            }
        }

        generatedElement.annotate(SuppressWarnings.class).param("value", annotationValue);
    }

    /**
     * addTrimmedDocComment
     *
     * @param method method
     * @param docComment docComment
     */
    public void addTrimmedDocComment(JMethod method, String docComment) {
        if (docComment != null) {
            method.javadoc().append(docComment.replaceAll("\r", "").trim());
        }
    }
}
